package com.linsen.core.log.aspect;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.linsen.core.common.aspect.BaseAspect;
import com.linsen.core.common.config.ModuleConfig;
import com.linsen.core.common.constant.LinsenConstant;
import com.linsen.core.common.enums.LoginEndpointType;
import com.linsen.core.common.enums.ModuleService;
import com.linsen.core.common.model.User;
import com.linsen.core.common.util.RequestHolder;
import com.linsen.core.common.util.SecurityUtil;
import com.linsen.core.common.util.ThrowableUtil;
import com.linsen.core.common.util.TraceUtil;
import com.linsen.core.log.annotation.Log;
import com.linsen.core.log.enums.LogType;
import com.linsen.core.log.event.LogEvent;
import com.linsen.core.redis.util.RedisSecurityUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpHeaders;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * @author zhaoyong
 * @date 2023/3/13
 */
@Aspect
@Slf4j
@RequiredArgsConstructor
public class LogAspect extends BaseAspect {
    private final ApplicationContext applicationContext;
    private final RedisSecurityUtil redisSecurityUtil;
    private final ModuleConfig moduleConfig;

    @Pointcut("@annotation(com.linsen.core.log.annotation.Log)")
    public void pointcut() {

    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Object result;
        //　获取request
        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        // 判断为空则直接跳过执行
        if (ObjectUtil.isEmpty(request)) {
            return point.proceed();
        }
        //　获取注解里的value值
        Method targetMethod = super.getMethod(point);
        Log logAnn = targetMethod.getAnnotation(Log.class);
        // 打印执行时间
        long startTime = System.nanoTime();
        // 请求方法
        String method = request.getMethod();
        String url = request.getRequestURI();

        // 获取IP
        String ip = super.getClientIP(request);

        // 参数
        Object[] args = point.getArgs();
        String params = super.getArgs(args, request);

        // 获取用户ID
        String userId = this.getUserId(request);
        String username = this.getUsername(request);

        // 计算耗时
        long tookTime = 0L;
        try {
            result = point.proceed();
        } finally {
            tookTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
        }

        //判断终端类型
        ModuleService moduleService = moduleConfig.getModule();
        LoginEndpointType loginEndpointType = LoginEndpointType.getByModuleService(moduleService);

        //　封装Log
        com.linsen.core.log.model.Log logModel = new com.linsen.core.log.model.Log()
                .setLogType(LogType.OPERATION)
                .setEndpointType(loginEndpointType)
                .setTraceId(request.getHeader(LinsenConstant.LINSEN_TRACE_ID))
                .setModuleService(moduleService)
                .setTitle(logAnn.value())
                .setOperation(JSONObject.toJSONString(result))
                .setMethod(method)
                .setUrl(url)
                .setParams(JSON.toJSONString(params))
                .setIp(ip)
                .setExecuteTime(tookTime)
                .setUserAgent(request.getHeader(HttpHeaders.USER_AGENT))
                .setCreateBy(userId)
                .setCreateByName(username)
                .setCreateTime(LocalDateTime.now());
         log.info("Http Request: {}", JSONObject.toJSONString(logModel));
        // 发布事件
        this.applicationContext.publishEvent(new LogEvent(logModel));

        return result;
    }

    /**
     * 配置异常通知
     *
     * @param point join point for advice
     * @param e     exception
     */
    @AfterThrowing(pointcut = "pointcut()", throwing = "e")
    public void afterThrowing(JoinPoint point, Throwable e) {
        // 打印执行时间
        long startTime = System.nanoTime();

        com.linsen.core.log.model.Log logModel = new com.linsen.core.log.model.Log();

        //　获取request
        HttpServletRequest request = RequestHolder.getHttpServletRequest();

        // 获取IP
        String ip = super.getClientIP(request);

        // 参数
        Object[] args = point.getArgs();
        String params = super.getArgs(args, request);

        // 请求方法
        String method = request.getMethod();
        String url = request.getRequestURI();

        //　获取注解里的value值
        Method targetMethod = super.getMethod((ProceedingJoinPoint) point);
        Log logAnn = targetMethod.getAnnotation(Log.class);

        //判断终端类型
        ModuleService moduleService = moduleConfig.getModule();
        LoginEndpointType loginEndpointType = LoginEndpointType.getByModuleService(moduleService);

        logModel.setExecuteTime(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime))
                .setLogType(LogType.ERROR)
                .setEndpointType(loginEndpointType)
                .setTraceId(TraceUtil.getTraceId(request))
                .setModuleService(moduleService)
                .setTitle(logAnn.value())
                .setMethod(method)
                .setUrl(url)
                .setParams(JSON.toJSONString(params))
                .setIp(ip)
                .setException(ThrowableUtil.getStackTrace(e))
                .setUserAgent(request.getHeader(HttpHeaders.USER_AGENT))
                .setCreateBy(this.getUserId(request))
                .setCreateByName(this.getUsername(request))
                .setCreateTime(LocalDateTime.now());
        //设置MDC
        TraceUtil.mdcTraceId(TraceUtil.getTraceId(request));
        // 发布事件
        log.info("Error Result: {}", logModel);
        this.applicationContext.publishEvent(new LogEvent(logModel));
    }

    private String getUserId(HttpServletRequest request) {
        // 判断header是否存在，存在则获取用户Id
        String headerToken = Optional.ofNullable(SecurityUtil.getHeaderToken(request)).orElse(SecurityUtil.getQueryToken(request));
        if (StrUtil.isNotBlank(headerToken)) {
//            Result userResult = this.authFeignClient.getUserByTokenValue(headerToken);
//            User user = userResult.getData(User.class);
            User user = this.redisSecurityUtil.getUser(headerToken);
            if (ObjectUtil.isNotNull(user)) {
                return user.getUserid();
            }
        }

        return null;
    }
    private String getUsername(HttpServletRequest request) {
        // 判断header是否存在，存在则获取用户Id
        String headerToken = Optional.ofNullable(SecurityUtil.getHeaderToken(request)).orElse(SecurityUtil.getQueryToken(request));
        if (StrUtil.isNotBlank(headerToken)) {
//            Result userResult = this.authFeignClient.getUserByTokenValue(headerToken);
//            User user = userResult.getData(User.class);
            User user = this.redisSecurityUtil.getUser(headerToken);
            if (ObjectUtil.isNotNull(user)) {
                return user.getUsername();
            }
        }

        return null;
    }
}
